package com.maun.test;

import com.maun.maun.Maun;
import com.maun.maun.MaunFactory;
import com.maun.maun.MaunMySql;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectOutputStream;
import java.io.Serializable;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.ResultSetMetaData;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.Date;
import java.util.Properties;
import java.util.Random;
import org.junit.After;
import org.junit.Before;

/**
 * Base class for {@link MaunMySql} tests.
 *
 * @author Kir Sorokin, kir.sorokin@integrated-apps.com
 */
@SuppressWarnings("CallToThreadDumpStack")
public abstract class MySqlBase {

    /**
     * Properties used for connecting to the database and setting up {@link MaunMySql}.
     */
    protected Properties properties;

    /**
     * Connection to the database (which Maun will be working with), which can be used to check
     * whether maun correctly inserted data.
     */
    protected Connection connection;

    /**
     * Instance of {@link MaunMySql} which is being tested.
     */
    protected Maun maun;

    /**
     * Instantiates {@link MaunMySql} and a connection to the database. This is run before every
     * test.
     *
     * @throws Exception 
     *      If anything went wrong.
     */
    @Before
    public void setup(
            ) throws Exception {

        properties = new MySqlTestProperties();

        Class.forName("com.mysql.jdbc.Driver");
        connection = DriverManager.getConnection(
                "jdbc:mysql://"
              + "" + properties.get("mysql.host") + ":" + properties.get("mysql.port") + "/"
              + "" + properties.get("mysql.database"),
                properties.getProperty("mysql.username"),
                properties.getProperty("mysql.password"));

        maun = MaunFactory.getInstance("mysql", properties);
    }

    /**
     * Closes the tested instance of {@link MaunMySql} and the connection to the database. This is
     * run after each test.
     *
     * @throws Exception 
     *      If anything went wrong.
     */
    @After
    public void teardown(
            ) throws Exception {

        if (connection != null) {
            try {
                connection.close();
            } catch (SQLException e) {
                e.printStackTrace(); // It's only a test, no need for complex logging here.
            }
        }
        if (maun != null) {
            maun.close();
        }
    }

    // Protected -----------------------------------------------------------------------------------

    protected void deleteTable(
            final Connection connection,
            final String table) throws Exception {

        Statement statement = null;
        try {
            statement = connection.createStatement();
            final int result = statement.executeUpdate("drop table if exists " + table);

            if (result != 0) {
                System.out.println(
                        "Failed to delete test table '" + table + "'. "
                      + "MySQL returned a non-zero affected-rows value.");
            }
        } finally {
            if (statement != null) {
                try {
                    statement.close();
                } catch (SQLException e) {
                    e.printStackTrace(); // It's only a test, no need for complex logging here.
                }
            }
        }
    }

    protected boolean hasColumn(
            final ResultSet resultSet,
            final String column) throws Exception {

        final ResultSetMetaData metaData = resultSet.getMetaData();
        for (int i = 1; i <= metaData.getColumnCount(); i++) {
            if (metaData.getColumnName(i).equals(column)) {
                return true;
            }
        }

        return false;
    }

    protected String normalizeForComparison(
            final String string) {

        return string.replaceAll("\\s+", "").toLowerCase();
    }

    protected void gererateTestSet(
            final File file,
            final int size) throws Exception {

        ObjectOutputStream oos = null;
        try {
            oos = new ObjectOutputStream(new FileOutputStream(file));

            for (int i = 0; i < size; i++) {
                oos.writeObject(generateEntity());
                oos.flush();
                oos.reset();
            }
        } finally {
            if (oos != null) {
                try {
                    oos.close();
                } catch (IOException e) {
                    // Ignore.
                }
            }
        }
    }

    protected Base_Test_Entity generateEntity(
            ) {

        return new Base_Test_Entity(
                randomString(MAX_STRING_LENGTH),
                randomString(MAX_STRING_LENGTH),
                randomString(MAX_STRING_LENGTH),
                randomString(MAX_STRING_LENGTH),
                randomString(MAX_STRING_LENGTH));
    }

    protected String randomString(
            final int maxLength) {

        final Random random = new Random(new Date().getTime());

        final int length = random.nextInt(maxLength);

        final StringBuilder builder = new StringBuilder(length);
        for (int i = 0; i < length; i++) {
            builder.append(CHARS[random.nextInt(CHARS.length)]);
        }

        return builder.toString();
    }

    protected String formatElapsed(
            final long start,
            final long end) {

        final long elapsed = end - start;
        final long minutes = elapsed / 60000L;
        final long seconds = elapsed % 60000L / 1000L;
        final long millis = elapsed % 1000L;

        return "" + minutes + ":"
             + "" + (seconds < 10 ? "0" + seconds : seconds) + "."
             + "" + (millis < 10 ? "00" + millis : (millis < 100 ? "0" + millis : millis)) + "s "
             + "(" + elapsed + "ms).";
    }

    ////////////////////////////////////////////////////////////////////////////////////////////////
    // Inner classes

    public static final class Base_Test_Entity implements Serializable {

        private String string1Field;
        private String string2Field;
        private String string3Field;
        private String string4Field;
        private String string5Field;

        public Base_Test_Entity(
                ) {

            // Does nothing.
        }

        public Base_Test_Entity(
                final String string1Field,
                final String string2Field,
                final String string3Field,
                final String string4Field,
                final String string5Field) {

            this.string1Field = string1Field;
            this.string2Field = string2Field;
            this.string3Field = string3Field;
            this.string4Field = string4Field;
            this.string5Field = string5Field;
        }

        public String getString1Field(
                ) {

            return string1Field;
        }

        public void setString1Field(
                final String string1Field) {

            this.string1Field = string1Field;
        }

        public String getString2Field(
                ) {

            return string2Field;
        }

        public void setString2Field(
                final String string2Field) {

            this.string2Field = string2Field;
        }

        public String getString3Field(
                ) {

            return string3Field;
        }

        public void setString3Field(
                final String string3Field) {

            this.string3Field = string3Field;
        }

        public String getString4Field(
                ) {

            return string4Field;
        }

        public void setString4Field(
                final String string4Field) {

            this.string4Field = string4Field;
        }

        public String getString5Field(
                ) {

            return string5Field;
        }

        public void setString5Field(
                final String string5Field) {

            this.string5Field = string5Field;
        }

    }

    ////////////////////////////////////////////////////////////////////////////////////////////////
    // Constants

    public static final int MAX_STRING_LENGTH = 4096;

    public static final String[] CHARS = new String[] {
        "a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k",
        "l", "m", "n", "o", "p", "q", "r", "s", "t", "u", "v",
        "w", "x", "y", "z", 
        "A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K",
        "L", "M", "N", "O", "P", "Q", "R", "S", "T", "U", "V",
        "W", "X", "Y", "Z",
    };

}
